/* Start Header
*****************************************************************/
/*!
\file   TSSocket.cpp
\author <Liu Wei Long, weilong.liu, 250002510>
        <Liu Zhi Yang, zhiyang.liu, 300000111>
\par    <email: weilong.liu\@digipen.edu> 
        <email: zhiyang.liu\@digipen.edu> 
\date   <Apr 3, 2012>
\brief
Copyright (C) 2012 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the
prior written consent of DigiPen Institute of Technology is prohibited.
*/
/* End Header
*******************************************************************/
#include "TSSocket.h"
#include "CommonMessages.h"

TSSocket::TSSocket(void)
{
  // Initialize critical sections
  InitializeCriticalSection(&mCSLockSend);
  InitializeCriticalSection(&mCSLockRecv);
}

TSSocket::TSSocket(SOCKET socket)
	: mSocket(socket)
{
  // Initialize critical sections
  InitializeCriticalSection(&mCSLockSend);
  InitializeCriticalSection(&mCSLockRecv);
}

TSSocket::~TSSocket()
{
	// Destroy socket
	if(mSocket != INVALID_SOCKET)
	{
		closesocket(mSocket);
		shutdown(mSocket, SD_BOTH);
	}
	// Destroy critical sections
	DeleteCriticalSection(&mCSLockSend);
	DeleteCriticalSection(&mCSLockRecv);
}

// Server-side Functionalities
bool TSSocket::SetUpUDPServerAddress(unsigned int &port_num)
{
	struct addrinfo hints, *result_info;

	SecureZeroMemory(&hints, sizeof(hints));

	hints.ai_family = AF_INET;			// AF_INET - Using IPv4 address family. 
	hints.ai_socktype = SOCK_DGRAM;		// Provides sequenced, reliable, two-way, connection-based byte streams
	hints.ai_protocol = IPPROTO_UDP;	// The protocol type is UDP
	hints.ai_flags = AI_PASSIVE;		// This flag is used later in getaddrinfo function.
										// It indicates that the caller intends to use the returned socket 
										// address structure in a call to the bind function.

	// Convert port to string
	char portnum_str[32];
	_itoa_s(port_num, portnum_str, 10);

	int result = getaddrinfo(NULL, portnum_str, &hints, &result_info);
	if (result)
	{
		std::cerr << "getaddrinfo failed: " << result << std::endl;
		WSACleanup();
		return false;
	}

	// Get IP Address
	const unsigned int servermaxsize = 32;
	const unsigned int hostmaxsize = 1025;
	char hostname[hostmaxsize];
	char servername[servermaxsize];
	DWORD ret;

	ret = getnameinfo(result_info->ai_addr, sizeof(struct sockaddr), hostname, hostmaxsize, servername, servermaxsize, NI_NUMERICSERV);
	struct addrinfo* serverinfo2;
	getaddrinfo(hostname, NULL, &hints, &serverinfo2);

	struct sockaddr_in* addr = (struct sockaddr_in*) serverinfo2->ai_addr;
	mIPAddress = inet_ntoa(addr->sin_addr);

	freeaddrinfo(serverinfo2);

	mSocket = socket(result_info->ai_family, 
					 result_info->ai_socktype, 
					 result_info->ai_protocol);

	if (mSocket == INVALID_SOCKET)
	{
		std::cerr << "Error while trying to create socket() " << WSAGetLastError() << std::endl;
		return false;
	}

	sockaddr_in recvaddr;
	recvaddr.sin_family = AF_INET;
	recvaddr.sin_port = htons(port_num);
	recvaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	result = bind(mSocket, (SOCKADDR*)&recvaddr, sizeof(recvaddr));
	if (result == SOCKET_ERROR)
	{
		std::cerr << "Error while trying to bind socket: " << WSAGetLastError() << std::endl;
		return false;
	}

    std::cout << "Server IP Address: " << mIPAddress << std::endl;
    std::cout << "Port number: " << port_num << std::endl;

	return true;
}


int TSSocket::SetUpUDPClientAddress(const char* portnumber)
{
  int result = 0;
  //result_info is a linked list that's going to be allocated and filled in with address info
  //hints is the configuration for storing data into result_info when calling getaddrinfo
  struct addrinfo *result_info = NULL;
  struct addrinfo hints;  

  // UDP Socket setup
  //Sets memory in hints to zero. Important step, so hints is not filled with garbage
  SecureZeroMemory(&hints, sizeof(hints));
  //Configuration for UDP Socket. 
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_DGRAM;
  hints.ai_protocol = IPPROTO_UDP;

  result = getaddrinfo("", portnumber, &hints, &result_info);
  // Checks if getaddrinfo failed
  if(result)
  {
    std::cerr << "getaddrinfo failed: " << result << std::endl;
    return 0; 
  }

  // Create a UDP SOCKET
  mSocket = socket(result_info->ai_family, result_info->ai_socktype, 
              result_info->ai_protocol);

  if(mSocket == INVALID_SOCKET) 
  {
    printf("Cannot create UDP socket %ld\n", WSAGetLastError());
    freeaddrinfo(result_info);
    return 0;
  }
  sockaddr_in RecvAddr;
  // Bind the socket to ANY address and the specified port.
  RecvAddr.sin_family = AF_INET;
  RecvAddr.sin_port = htons(static_cast<u_short>(atoi(portnumber)));
  RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  //bind our UDP socket to local endpoint
  result = bind(mSocket, (SOCKADDR *) & RecvAddr, sizeof (RecvAddr));
  if (result != 0) 
  {
    wprintf(L"bind failed with error %d\n", WSAGetLastError());
    return 0;
  }

  return 1;
}

int TSSocket::SendUDPData(const char* data, int numOfBytes, const std::string& ip_address, int port_num)
{
  EnterCriticalSection(&mCSLockSend);
  sockaddr_in RecvAddr;
  // Set up the RecvAddr structure with the IP address of
  // the receiver (in this example case "192.168.1.1")
  // and the specified port number.
  RecvAddr.sin_family = AF_INET;
  RecvAddr.sin_port = ntohs(port_num);
  RecvAddr.sin_addr.s_addr = inet_addr(ip_address.c_str());
  /* Tranmsit data to get time */
  // sendto automatically binds address to the sending scket
  int bytesSent = sendto(mSocket, data, numOfBytes, 0, (SOCKADDR *)&RecvAddr, sizeof (RecvAddr));
  
  LeaveCriticalSection(&mCSLockSend);

  if (bytesSent)
	  return bytesSent;
  else 
  {
      wprintf(L"sendto failed with error: %d\n", WSAGetLastError());
      return SOCKET_ERROR;
  }
}

bool TSSocket::RecvUDPData(char* data, int& numOfBytesRead)
{
  EnterCriticalSection(&mCSLockRecv);
  // Receive data
  char buffer[UDP_RECV_BUFFER_SIZE];
  sockaddr_in SenderAddr; // Used for storing the sender's address
  socklen_t SenderAddrSize = sizeof (SenderAddr);
  int bytesRead = recvfrom(mSocket, buffer, UDP_RECV_BUFFER_SIZE, 0, (SOCKADDR *)&SenderAddr, &SenderAddrSize);
  if (bytesRead == SOCKET_ERROR || bytesRead == 0)
  {
    numOfBytesRead = 0;
    
    int errcode = WSAGetLastError();
    LeaveCriticalSection(&mCSLockRecv);
    printf("recvfrom failed with error %d\n", errcode);
    return false;
  }

  memcpy(data, buffer, bytesRead);
  numOfBytesRead = bytesRead;
  LeaveCriticalSection(&mCSLockRecv);
  return true;
}